﻿/*
BF4Rcon.NET, provides a .NET implementation of BF4's RCON interface.
Copyright (C) 2013 Tim Marro

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using TimScripts.BF4Rcon.Frostbite;
using System.IO;

namespace TimScripts.BF4Rcon.Test
{
    [TestFixture]
    public class StreamRconConnectionTests
    {
        //only being used to get packet bytes
        PacketConverter Converter = new PacketConverter(() => null, () => null);
        TestStreamRconConnection Connection;

        [SetUp]
        public void SetUp()
        {
            Connection = new TestStreamRconConnection();
            Connection.Connect();
        }



        [Test]
        public void SendPacketSendsPacket()
        {
            Packet p = new Packet(true, false, 1, "a");
            byte[] expected = Converter.GetPacketBytes(p);
            byte[] actual;

            Connection.SendPacket(p);
            actual = Connection.GetStreamData();

            Assert.AreEqual(expected, actual);
        }

        [Test]
        public async Task SendPacketSendsPacketAsync()
        {
            Packet p = new Packet(true, false, 1, "a");
            byte[] expected = Converter.GetPacketBytes(p);
            byte[] actual;

            await Connection.SendPacketAsync(p);
            actual = Connection.GetStreamData();

            Assert.AreEqual(expected, actual);
        }

        [Test]
        public void ReceivePacketReturnsPacket()
        {
            Packet p = new Packet(true, false, 1, "a");
            Packet result;

            Connection.SendPacket(p);
            Connection.SetToStart();

            result = Connection.ReceivePacket();

            Assert.True(p.PacketEquals(result));
        }

        [Test]
        public async Task ReceivePacketReturnsPacketAsync()
        {
            Packet p = new Packet(true, false, 1, "a");
            Packet result;

            Connection.SendPacket(p);
            Connection.SetToStart();

            result = await Connection.ReceivePacketAsync();

            Assert.True(p.PacketEquals(result));
        }

        [Test]
        public void ReceivePacketReturnsLargePacket()
        {
            string longWord;
            Packet packet;
            Packet result;

            //longer than internal buffer sizes
            longWord = new String(Enumerable.Repeat('a', 16000).ToArray());
            packet = new Packet(true, false, 1, longWord);

            Connection.SendPacket(packet);
            Connection.SetToStart();

            result = Connection.ReceivePacket();

            Assert.True(packet.PacketEquals(result));
        }

        [Test]
        public async Task ReceivePacketReturnsLargePacketAsync()
        {
            string longWord;
            Packet packet;
            Packet result;

            //longer than internal buffer sizes
            longWord = new String(Enumerable.Repeat('a', 16000).ToArray());
            packet = new Packet(true, false, 1, longWord);

            Connection.SendPacket(packet);
            Connection.SetToStart();

            result = await Connection.ReceivePacketAsync();

            Assert.True(packet.PacketEquals(result));
        }

        [Test]
        public void CanReceiveMultiplePackets()
        {
            Packet p = new Packet(true, false, 1, "a");
            Packet result;

            Connection.SendPacket(p);
            Connection.SendPacket(p);
            Connection.SetToStart();

            result = Connection.ReceivePacket();

            Assert.True(p.PacketEquals(result));
        }

        [Test]
        public async Task CanReceiveMultiplePacketsAsync()
        {
            Packet p = new Packet(true, false, 1, "a");
            Packet result;

            Connection.SendPacket(p);
            Connection.SendPacket(p);
            Connection.SetToStart();

            result = await Connection.ReceivePacketAsync();

            Assert.True(p.PacketEquals(result));
        }

        [Test]
        public void SendThrowsWhenStreamNotSet()
        {
            TestStreamRconConnection conn = new TestStreamRconConnection(false);
            Packet p = new Packet(true, false, 1, "a");

            Assert.Throws<StreamNotSetException>(() => conn.SendPacket(p));
            Assert.Throws<StreamNotSetException>(async () => await conn.SendPacketAsync(p));
        }

        [Test]
        public void ReceiveThrowsWhenStreamNotSet()
        {
            TestStreamRconConnection conn = new TestStreamRconConnection(false);

            Assert.Throws<StreamNotSetException>(() => conn.ReceivePacket());
            Assert.Throws<StreamNotSetException>(async () => await conn.ReceivePacketAsync());
        }

        [Test]
        public void MustBeConnected()
        {
            TestStreamRconConnection conn = new TestStreamRconConnection();
            Packet p = new Packet(true, false, 1, "a");

            Assert.Throws<DisconnectionException>(() => conn.ReceivePacket());
            Assert.Throws<DisconnectionException>(() => conn.SendPacket(p));
            Assert.Throws<DisconnectionException>(async () => await conn.ReceivePacketAsync());
            Assert.Throws<DisconnectionException>(async () => await conn.SendPacketAsync(p));
        }

        private class TestStreamRconConnection : StreamRconConnection
        {
            private bool _IsConnected = false;
            MemoryStream Stream = new MemoryStream();

            public override void Connect()
            {
                _IsConnected = true;
            }

            public override void Disconnect()
            {
                _IsConnected = false;
            }

            public override bool IsConnected
            {
                get { return _IsConnected; }
            }

            public TestStreamRconConnection()
                : this(true)
            {

            }

            public TestStreamRconConnection(bool setStream)
            {
                if (setStream) SetStream(Stream);
            }

            internal byte[] GetStreamData()
            {
                return Stream.ToArray();
            }

            internal void SetToStart()
            {
                Stream.Position = 0;
            }
        }

        private class TestNotConnectedException : Exception
        {

        }
    }
}
